package org.dhis2.data.dhislogic

import org.dhis2.commons.Constants
import org.dhis2.utils.DateUtils
import org.hisp.dhis.android.core.enrollment.Enrollment
import org.hisp.dhis.android.core.maintenance.D2Error
import org.hisp.dhis.android.core.program.ProgramStage
import timber.log.Timber
import java.util.Calendar

class EnrollmentEventGenerator(
    private val generatorRepository: EnrollmentEventGeneratorRepository,
) {

    fun generateEnrollmentEvents(enrollmentUid: String): Pair<String, String?> {
        return generatorRepository.enrollment(enrollmentUid)?.let { enrollment ->
            autogeneratedEvents(enrollment)
            firstStageToOpen(enrollment)
        } ?: Pair(enrollmentUid, null)
    }

    private fun autogeneratedEvents(enrollment: Enrollment) {
        val autogeneratedEvents = generatorRepository.enrollmentAutogeneratedEvents(
            enrollment.uid(),
            enrollment.program()!!,
        )
        autogeneratedEvents.forEach { programStage ->
            generateScheduledEvent(enrollment, programStage)
        }
    }

    private fun firstStageToOpen(enrollment: Enrollment): Pair<String, String?> {
        val program = generatorRepository.enrollmentProgram(enrollment.program()!!)
        val stageToOpen = if (program?.useFirstStageDuringRegistration() == true) {
            generatorRepository.firstStagesInProgram(enrollment.program()!!)
        } else {
            generatorRepository.firstOpenAfterEnrollmentStage(enrollment.program()!!)
        }
        return stageToOpen?.let {
            checkIfEventExist(enrollment, stageToOpen)
        } ?: Pair(enrollment.uid(), null)
    }

    private fun checkIfEventExist(
        enrollment: Enrollment,
        programStage: ProgramStage,
    ): Pair<String, String?> {
        if (!generatorRepository.eventExistInEnrollment(enrollment.uid(), programStage.uid())) {
            generateOpenEvent(enrollment, programStage)
        }
        val eventUidToOpen = generatorRepository.eventUidInEnrollment(
            enrollment.uid(),
            programStage.uid(),
        )
        return Pair(enrollment.uid(), eventUidToOpen)
    }

    private fun generateScheduledEvent(enrollment: Enrollment, programStage: ProgramStage) {
        try {
            val eventUid = generatorRepository.addEvent(
                enrollment.uid(),
                enrollment.program()!!,
                programStage.uid(),
                enrollment.organisationUnit()!!,
            )

            val periodType = programStage.periodType()
            val minDaysFromStart = programStage.minDaysFromStart() ?: 0
            val calendar = DateUtils.getInstance().calendar
            val generateByEnrollmentDate = programStage.generatedByEnrollmentDate() ?: false

            val now = Calendar.getInstance()
            now.set(Calendar.HOUR_OF_DAY, 0)
            now.set(Calendar.MINUTE, 0)
            now.set(Calendar.SECOND, 0)
            now.set(Calendar.MILLISECOND, 0)

            val generationDate =
                if (!generateByEnrollmentDate && enrollment.incidentDate() != null) {
                    enrollment.incidentDate()
                } else {
                    enrollment.enrollmentDate()
                }
            generationDate?.let { date -> calendar.time = date }

            calendar.set(Calendar.HOUR_OF_DAY, 0)
            calendar.set(Calendar.MINUTE, 0)
            calendar.set(Calendar.SECOND, 0)
            calendar.set(Calendar.MILLISECOND, 0)
            calendar.add(Calendar.DATE, minDaysFromStart)
            var dueDate = calendar.time

            periodType?.let { dueDate = generatorRepository.periodStartingDate(it, dueDate) }
            val isOverdue = dueDate.before(now.time)
            generatorRepository.setDueDate(eventUid, dueDate, isOverdue)
        } catch (d2Error: D2Error) {
            Timber.e(d2Error)
        }
    }

    private fun generateOpenEvent(enrollment: Enrollment, programStage: ProgramStage) {
        try {
            val eventUid = generatorRepository.addEvent(
                enrollment.uid(),
                enrollment.program()!!,
                programStage.uid(),
                enrollment.organisationUnit()!!,
            )

            val periodType = programStage.periodType()
            val calendar = DateUtils.getInstance().calendar

            val reportDate = when (programStage.reportDateToUse()) {
                Constants.ENROLLMENT_DATE -> enrollment.enrollmentDate()
                else -> enrollment.incidentDate()
            }
            reportDate?.let { date -> calendar.time = date }

            calendar.set(Calendar.HOUR_OF_DAY, 0)
            calendar.set(Calendar.MINUTE, 0)
            calendar.set(Calendar.SECOND, 0)
            calendar.set(Calendar.MILLISECOND, 0)
            var eventDate = calendar.time

            periodType?.let { eventDate = generatorRepository.periodStartingDate(it, eventDate) }

            generatorRepository.setEventDate(eventUid, eventDate)
        } catch (d2Error: D2Error) {
            Timber.e(d2Error)
        }
    }
}
